﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System.Runtime.Serialization;
using System;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BrainTechLLC.ThreadSafeObjects;

namespace BrainTechLLC
{
    public class CombatStateInfo
    {
        public long Source { get; set; }
        public long Attacking { get; set; }
        public PropertyData args { get; set; }
        public PropertyData modifyingArguments { get; set; }
        public int DelayBetweenRounds { get; set; }
        public int Delay { get; set; }
    }

    public class CounterAttack
    {
        public long Attacked { get; set; }
        public long AttackedBy { get; set; }
    }

    public class CombatHandler
    {
        public static CombatHandler Instance;
        public ThreadSafeLookup<long, CombatStateInfo> CombatStates = new ThreadSafeLookup<long, CombatStateInfo>();
        public ThreadSafeListAndLookup<long, CombatStateInfo> BeingAttacked = new ThreadSafeListAndLookup<long, CombatStateInfo>();

        public RepeatingTask Tasks;

        public void Initialize()
        {
            Instance = this;

            Tasks = new RepeatingTask()
            {
                Schedule = new ScheduledTime(100, ScheduleOptions.ScheduleRepeatingMS),
                Actions = new ActionAndOrderCollection()
            };

            DynamicMethod method = DynamicMethod.Create(this.GetType().GetMethod("HandleMainCombatTick"), typeof(Action));
            Tasks.Actions.Add(ActionAndOrder.Create(method, 50, "HandleMainCombatTick"));
            Globals.SchedulingEngine.ScheduleEventToExecute(Tasks, 100);
        }

        public static void HandleMainCombatTick()
        {
            List<CounterAttack> counters = new List<CounterAttack>();
            ExecuteAttacks(ref counters);
            ExecuteCounterAttacks(counters);
        }

        public CombatStateInfo CreateCombatStateInfo(long sourceID, long targetID)
        {
            CombatStateInfo info = new CombatStateInfo()
            {
                Source = sourceID,
                Attacking = targetID,
                DelayBetweenRounds = 15
            };

            CombatStates.AddOrSet(sourceID, info);
            BeingAttacked.Add(targetID, info);
            return info;
        }

        public CombatStateInfo RegisterNewAttack(GameChar source, GameChar target)
        {
            CombatStateInfo info = GetCombatState(source.AssociatedID);

            if (info == null)
                info = CreateCombatStateInfo(source.AssociatedID, target.AssociatedID);

            return info;
        }

        [MethodVisible]
        public static string CanAttack(CommandToPerform cmd, IGameThing src, IGameThing tar, PropertyData args, PropertyData modifyingArguments)
        {
            GameChar source = src as GameChar;
            GameChar target = tar as GameChar;

            if (source == null || target == null)
                return "Error";

            if (source.LocationInt != target.LocationInt)
                return "That opponent is not here.";

            if (target.IsDead)
                return "The opponent is dead.";

            if (source.IsDead)
                return "You are dead.";

            return string.Empty;
        }

        [MethodVisible]
        public static List<PerceivedEventAndAudience> CounterAttack(GameChar newAttacker, GameChar attackedBy)
        {
            List<PerceivedEventAndAudience> events = new List<PerceivedEventAndAudience>();

            CombatStateInfo info = Instance.CreateCombatStateInfo(newAttacker.AssociatedID, attackedBy.AssociatedID);

            EventToPerceive evt = CreateCounterAttackObservableEvent(newAttacker, attackedBy);
            events.Add(new PerceivedEventAndAudience() { Event = evt, Audience = newAttacker.GameLocation.Things });
            return events;
        }

        [MethodVisible]
        public static List<PerceivedEventAndAudience> Attack(GameChar source, GameChar target, PropertyData args, PropertyData modifyingArguments)
        {
            List<PerceivedEventAndAudience> events = new List<PerceivedEventAndAudience>();

            // For both attacker and target:
            // Allow State influence
            // Allow Ability influence
            // Allow (equipped?) Object influence
            IdentifierAndValue movementPointCost = new IdentifierAndValue("MovementPointCost", 2);
            IdentifierAndValue damagePhysical = new IdentifierAndValue("DamagePhysical", 1);
            PropertyData parameters = new PropertyData(movementPointCost, damagePhysical);
            AccountForInfluences("Attack", source, target, parameters);

            source.EnqueueModification(CommandFactory.AddOrSubtractFromProperty("MovementPoints", 0 - movementPointCost.Value.Int()));
            target.EnqueueModification(CommandFactory.AddOrSubtractFromProperty("HitPoints", 0 - damagePhysical.Value.Int()));

            EventToPerceive evt = CreateAttackObservableEvent(source, target);
            events.Add(new PerceivedEventAndAudience() { Event = evt, Audience = source.GameLocation.Things });
            //target.RegisterAttackFrom(source.AssociatedID);

            return events;
        }

        [MethodVisible]
        public static void StopAttacking(GameChar source, bool reciprocalRemove)
        {
            CombatHandler handler = Instance;
            handler.RemoveCombatState(source, reciprocalRemove);
        }

        [MethodVisible]
        public void RemoveCombatState(GameChar source, bool reciprocalRemove)
        {
            CombatStateInfo info = GetCombatState(source.AssociatedID);

            if (info == null)
                return;

            source.States.RemoveState("BasicAttackState");

            GameChar target = info.Attacking.Find<GameChar>();
            CombatStates.Remove(source.AssociatedID);

            BeingAttacked.Remove(target.AssociatedID, (s) => s.Attacking == target.AssociatedID);

            if (reciprocalRemove)
            {
                CombatStateInfo info2 = GetCombatState(target.AssociatedID);
                if (info2 != null && info2.Attacking == source.AssociatedID)
                {
                    RemoveCombatState(target, reciprocalRemove);
                }
            }

            var attackers = BeingAttacked.Find(target.AssociatedID);
            if (attackers.Count == 0)
            {
                target.States.RemoveState("BeingAttackedState");
            }
        }

        [MethodVisible]
        public static void AccountForInfluences(string influenceName, GameChar source, GameChar target, PropertyData parameters)
        {
            Globals.InfluenceManager.AccountForInfluences(influenceName, source, target, parameters);
            source.InfluenceCollection.AccountForInfluences(influenceName, source, target, parameters);
            target.InfluenceCollection.AccountForInfluences(influenceName, source, target, parameters);
        }

        public static void ExecuteAttacks(ref List<CounterAttack> counters)
        {
            CombatHandler handler = Instance;
            AssociatedIDCollection attackerIDs = new AssociatedIDCollection(handler.CombatStates.Keys.ToArray());
            List<GameChar> attackers = attackerIDs.ReadItems<GameChar>();

            for (int n = 0; n < attackers.Count; n++)
            {
                GameChar attacker = attackers[n];
                CombatStateInfo info = handler.GetCombatState(attacker.AssociatedID);

                if (info == null)
                {
                    throw new Exception("blah");
                }

                if (info.Delay > 0)
                {
                    info.Delay--;
                    continue;
                }

                info.Delay = info.DelayBetweenRounds;
                GameChar victim = Everything.Find<GameChar>(info.Attacking);

                if (victim == null)
                {
                    throw new Exception("victim with id " + info.Attacking.ToString() + " not found");
                }

                if (attacker.LocationInt != victim.LocationInt)
                {
                    StopAttacking(attacker, true);
                }
                else
                {
                    var events = Attack(attacker, victim, info.args, info.modifyingArguments);

                    CombatStateInfo attackEntryForVictim = handler.GetCombatState(victim.AssociatedID);

                    if (attackEntryForVictim == null)
                    {
                        counters.Add(new CounterAttack() { Attacked = victim.AssociatedID, AttackedBy = attacker.AssociatedID });
                    }

                    if (events != null)
                    {
                        foreach (PerceivedEventAndAudience evt in events)
                        {
                            evt.Event.ToAudience(evt.Audience);
                        }
                    }
                }
            }
        }

        public static void ExecuteCounterAttacks(List<CounterAttack> counters)
        {
            for (int n = 0; n < counters.Count; n++)
            {
                CounterAttack c = counters[n];
                GameChar newAttacker = Everything.Find<GameChar>(c.Attacked);
                GameChar attackedBy = Everything.Find<GameChar>(c.AttackedBy);
                var events = CounterAttack(newAttacker, attackedBy);

                if (events != null)
                {
                    foreach (PerceivedEventAndAudience evt in events)
                    {
                        evt.Event.ToAudience(evt.Audience);
                    }
                }
            }
        }

        public CombatStateInfo GetCombatState(long id)
        {
            return CombatStates[id];
        }

        public List<CombatStateInfo> GetCombatStateForAttacked(long id)
        {
            return BeingAttacked.Find(id);
        }

        [MethodVisible]
        public static EventToPerceive CreateAttackObservableEvent(GameChar source, GameChar target)
        {
            return new GeneralEventToPerceive()
            {
                ShowToSource = new Event("You attack ~target.", ViewType.See),
                ShowToObservers = new Event("~source attacks ~target.", ViewType.See),
                ShowToTarget = new Event("~source attacks you!", ViewType.See),
                Sources = new AssociatedIDCollection(source.AssociatedID),
                Targets = new AssociatedIDCollection(target.AssociatedID)
            };
        }

        [MethodVisible]
        public static EventToPerceive CreateCounterAttackObservableEvent(GameChar source, GameChar target)
        {
            return new GeneralEventToPerceive()
            {
                ShowToSource = new Event("You begin attacking ~target.", ViewType.See),
                ShowToObservers = new Event("~source begins attacking ~target.", ViewType.See),
                ShowToTarget = new Event("~source begins attacking you!", ViewType.See),
                Sources = new AssociatedIDCollection(source.AssociatedID),
                Targets = new AssociatedIDCollection(target.AssociatedID)
            };
        }
    }
}
